package com.dh.example.netty.handler;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.dh.example.netty.enums.WsReqTypeEnum;
import com.dh.example.netty.pojo.WsAuthorize;
import com.dh.example.netty.pojo.request.WsBaseReq;
import com.dh.example.netty.service.WebSocketService;
import com.dh.example.netty.util.NettyUtil;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;


/**
 * netty web 业务处理器，里面处理客户端的事件
 *
 * @author yolo
 * @date 2024/03/14 13:01:15
 */
@Slf4j
@Sharable
public class NettyWebSocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    private WebSocketService webSocketService;

    /**
     * 已添加处理程序
     * 当web客户端连接后，触发该方法
     *
     * @param ctx ctx
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        this.webSocketService = getService();
    }

    /**
     * 处理程序已删除
     * 客户端离线
     *
     * @param ctx ctx
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        log.warn("[{}]触发 handlerRemoved 离线![{}]", DateUtil.now(), NettyUtil.getAttr(ctx.channel(), NettyUtil.USER_ID));
        userOffLine(ctx);
    }

    /**
     * 通道未激活
     * 取消绑定
     *
     * @param ctx ctx
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        // 可能出现业务判断离线后再次触发 channelInactive
        log.warn("[{}]触发 channelInactive 掉线![{}]", DateUtil.now(), NettyUtil.getAttr(ctx.channel(), NettyUtil.USER_ID));
        userOffLine(ctx);
    }

    /**
     * 用户事件已触发
     * 心跳检查
     *
     * @param ctx ctx
     * @param evt evt
     * @throws Exception 例外
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
            // 读空闲 长时间未通信，移除
            if (idleStateEvent.state() == IdleState.READER_IDLE) {
                // 关闭用户的连接
                userOffLine(ctx);
            }
        } else if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
            // websocket握手完成
            this.webSocketService.connect(ctx.channel());
            String token = NettyUtil.getAttr(ctx.channel(), NettyUtil.TOKEN);
            if (StrUtil.isNotBlank(token)) {
                this.webSocketService.authorize(ctx.channel(), new WsAuthorize(token));
            }else{
                log.info("用户：{} 无有token，关闭连接", NettyUtil.getAttr(ctx.channel(), NettyUtil.USER_ID));
            }
        }
        super.userEventTriggered(ctx, evt);
    }

    /**
     * 捕获异常
     *
     * @param ctx   ctx
     * @param cause 原因
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.warn("异常发生，异常消息exceptionCaught =[{}]", NettyUtil.getAttr(ctx.channel(), NettyUtil.USER_ID), cause);
        ctx.channel().close();
    }

    /**
     * 获取服务
     *
     * @return {@code WebSocketService}
     */
    private WebSocketService getService() {
        return SpringUtil.getBean(WebSocketService.class);
    }

    /**
     * 通道read0
     * 读取客户端发送的请求报文
     *
     * @param ctx ctx
     * @param msg 味精
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {

        log.info("发送到时间：【{}】内容：【{}】", DateUtil.now(), msg.text());
        WsBaseReq wsBaseReq;
        try {
            wsBaseReq = JSONUtil.toBean(msg.text(), WsBaseReq.class);
        } catch (Exception e) {
            ctx.channel().writeAndFlush(new TextWebSocketFrame(String.format("在刚刚[%s] 发送了一条信息【%s】 解析成WsBaseReq对象失败", DateUtil.format(DateUtil.date(), "HH:mm:ss"), msg.text())));
            return;
        }
        WsReqTypeEnum wsReqTypeEnum = WsReqTypeEnum.of(wsBaseReq.getType());
        if (wsReqTypeEnum == null) {
            ctx.channel().writeAndFlush(new TextWebSocketFrame(String.format("在刚刚[%s] 发送了type为null【%s】", DateUtil.format(DateUtil.date(), "HH:mm:ss"), JSONUtil.toJsonStr(wsBaseReq))));
            return;
        }
        switch (wsReqTypeEnum) {
            case LOGIN_URL:
                this.webSocketService.handleLoginReq(ctx.channel());
                log.info("请求二维码 = " + msg.text());
                break;
            case PING:
                ctx.channel().writeAndFlush(new TextWebSocketFrame(String.format("在刚刚[%s] 前端ping了下【%s】", DateUtil.format(DateUtil.date(), "HH:mm:ss"), JSONUtil.toJsonStr(wsBaseReq))));
                break;
            default:
                // 测试使用，正式未存在数据不处理
                //  break;
                ctx.channel().writeAndFlush(new TextWebSocketFrame(String.format("在刚刚[%s] 类型不存在【%s】", DateUtil.format(DateUtil.date(), "HH:mm:ss"), JSONUtil.toJsonStr(wsBaseReq))));
        }
    }

    /**
     * 用户离线
     *
     * @param ctx ctx
     */
    private void userOffLine(ChannelHandlerContext ctx) {
        this.webSocketService.removed(ctx.channel());
        ctx.channel().close();
    }

}
